﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Controls;
using System.Windows.Markup;
using RandomSearch.PluginsCommon;

namespace RandomSearch.Plugins.AssemblySearch {
    [Export(typeof(ISearchPlugin))]
    public class AssemblySearch : BaseSearchPlugin {
        private Dictionary<string, bool> _loadedAssemblies;
        public AssemblySearch() {
            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += CurrentDomain_ReflectionOnlyAssemblyResolve;
        }

        private Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args) {
            return Assembly.ReflectionOnlyLoad(args.Name);
        }

        private const string TextToSerachSettingName = "AssemblySearch_Class";
        private const string CaseSerachSettingName = "AssemblySearch_Case";

        public override string PluginId {
            get { return "AssemblySearch"; }
        }

        public override string PluginName {
            get { return "Assembly search"; }
        }

        public override string PatternToSearch {
            get { return "*.dll"; }
        }

        public override string Options {
            get {
                var pnl = new StackPanel();
                var lb = new Label { Content = "Class to search" };
                var tbText = new TextBox { Name = TextToSerachSettingName };
                pnl.Children.Add(lb);
                pnl.Children.Add(tbText);
                pnl.Children.Add(new CheckBox { Name = CaseSerachSettingName, Content = "Match case" });
                string settings = XamlWriter.Save(pnl);
                return settings;
            }
        }

        public override IEnumerable<SearchResult> Search(SearchOptions options) {

            string className = options.Settings[TextToSerachSettingName];
            bool matchCase = bool.Parse(options.Settings[CaseSerachSettingName]);

            //create list of already loaded assemblies
            var exists = new[]
                             {
                                 AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().Select(x=> new { Name = x.GetName().Name + ".dll", Value = ContainsType(x,matchCase,className) } ),
                                 AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().Select(x=>new { Name = x.GetName().Name + ".exe", Value = ContainsType(x,matchCase,className) } )
                                     
                             };
            
            _loadedAssemblies = exists.SelectMany(d => d).ToDictionary(p => p.Name, p => p.Value);

            return base.Search(options).Where(x => Filter(options, x));
        }

        public bool Filter(SearchOptions o, SearchResult file) {
            string className = o.Settings[TextToSerachSettingName];
            bool matchCase = bool.Parse(o.Settings[CaseSerachSettingName]);
            try {
                var fileName = Path.GetFileName(file.File);
                if (_loadedAssemblies.ContainsKey(fileName))
                    return _loadedAssemblies[fileName];

                Assembly assembly = Assembly.ReflectionOnlyLoadFrom(file.File);

                var res = ContainsType(assembly, matchCase, className);
                _loadedAssemblies.Add(fileName, res);
                return res;
            }
            catch (Exception) {
                return false;
            }
        }

        private bool ContainsType(Assembly assembly, bool matchCase, string className) {
            Type[] types = assembly.GetExportedTypes();
            StringComparer comparer = !matchCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal;

            IEnumerable<Type> res = from t in types
                                    where t.GetInterfaces()
                                           .Select(x => x.Name)
                                           .Contains(className, comparer)
                                    select t;
            bool b = res.Any();
            return b;
        }

        ~AssemblySearch() {
            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= CurrentDomain_ReflectionOnlyAssemblyResolve;
        }
    }
}